package com.gitee.wsl.compose.ui.richtext.content

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.sp
import com.gitee.wsl.compose.ui.richtext.theme.RichTextStringStyle
import com.gitee.wsl.compose.ui.richtext.block.code.DefaultCodeBlockBackgroundColor
import com.gitee.wsl.struct.generator.randomUUID

sealed class Format(private val simpleTag: String? = null) {

 internal open fun getStyle(
     richTextStyle: RichTextStringStyle,
     contentColor: Color
 ): SpanStyle? = null

  data object Italic : Format("italic") {
   internal val DefaultStyle = SpanStyle(fontStyle = FontStyle.Italic)
   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.italicStyle
 }

  data object Bold : Format(simpleTag = "foo") {
   internal val DefaultStyle = SpanStyle(fontWeight = FontWeight.Bold)
   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.boldStyle
 }

  data object Underline : Format("underline") {
   internal val DefaultStyle = SpanStyle(textDecoration = TextDecoration.Underline)
   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.underlineStyle
 }

  data object Strikethrough : Format("strikethrough") {
   internal val DefaultStyle = SpanStyle(textDecoration = TextDecoration.LineThrough)
   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.strikethroughStyle
 }

  data object Subscript : Format("subscript") {
   internal val DefaultStyle = SpanStyle(
       baselineShift = BaselineShift(-0.2f),
       // TODO this should be relative to current font size
       fontSize = 10.sp
   )

   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.subscriptStyle
 }

  data object Superscript : Format("superscript") {
   internal val DefaultStyle = SpanStyle(
       baselineShift = BaselineShift.Superscript,
       fontSize = 10.sp
   )

   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.superscriptStyle
 }

  data object Code : Format("code") {
   internal val DefaultStyle = SpanStyle(
       fontFamily = FontFamily.Monospace,
       fontWeight = FontWeight.Medium,
       background = DefaultCodeBlockBackgroundColor
   )

   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.codeStyle
 }

  data class Link(val destination: String) : Format() {
   override fun getStyle(
       richTextStyle: RichTextStringStyle,
       contentColor: Color
   ) = richTextStyle.linkStyle

   internal companion object {
     val DefaultStyle = SpanStyle(
         textDecoration = TextDecoration.Underline,
         color = Color.Blue
     )
   }
 }

 internal fun registerTag(tags: MutableMap<String, Any>): String {
   simpleTag?.let { return it }
   val uuid = randomUUID()
   tags[uuid] = this
   return "format:$uuid"
 }

 internal companion object {
   val FormatAnnotationScope = Format::class.qualifiedName!!

   // For some reason, if this isn't lazy, Bold will always be null. Is Compose messing up static
   // initialization order?
   private val simpleTags by lazy(LazyThreadSafetyMode.NONE) {
     listOf(Bold, Italic, Underline, Strikethrough, Subscript, Superscript, Code)
   }

   fun findTag(
     tag: String,
     tags: Map<String, Any>
   ): Format? {
     val stripped = tag.removePrefix("format:")
     return if (stripped === tag) {
       // If the original string was returned, it means the string did not have the prefix.
       simpleTags.firstOrNull { it.simpleTag == tag }
     } else {
       tags[stripped] as? Format
     }
   }
 }
}